package io.gitee.zhangbinhub.admin.component

import com.fasterxml.jackson.databind.ObjectMapper
import io.gitee.zhangbinhub.acp.boot.log.LogAdapter
import io.gitee.zhangbinhub.admin.constant.AcpConstant
import io.gitee.zhangbinhub.admin.vo.TokenUserInfoVo
import org.bouncycastle.util.encoders.Base64
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.oauth2.core.*
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication
import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal
import org.springframework.stereotype.Component
import java.time.Instant

@Component
class TokenTools(
    private val logAdapter: LogAdapter,
    private val objectMapper: ObjectMapper
) {
    fun convertClaimsSet(claims: MutableMap<String, Any?>): OAuth2AuthenticatedPrincipal {
        claims.computeIfPresent(
            AcpConstant.tokenClaimsAud, fun(_: String, value: Any?): Any? {
                return (value as? String)?.let { listOf(it) } ?: value
            }
        )
        claims.computeIfPresent(
            AcpConstant.tokenClaimsExp, fun(_: String, value: Any?): Any? {
                return value?.let {
                    Instant.ofEpochSecond((it as Number).toLong())
                }
            }
        )
        claims.computeIfPresent(
            AcpConstant.tokenClaimsIat, fun(_: String, value: Any?): Any? {
                return value?.let {
                    Instant.ofEpochSecond((it as Number).toLong())
                }
            }
        )
        claims.computeIfPresent(
            AcpConstant.tokenClaimsNbf, fun(_: String, value: Any?): Any? {
                return value?.let {
                    Instant.ofEpochSecond((it as Number).toLong())
                }
            }
        )
        claims.computeIfPresent(
            AcpConstant.tokenClaimsClientId, fun(_: String, value: Any?): Any? {
                return value?.toString()
            }
        )
        claims.computeIfPresent(
            AcpConstant.tokenClaimsIss, fun(_: String, value: Any?): Any? {
                return value?.toString()
            }
        )
        val authorities: MutableCollection<GrantedAuthority> = mutableListOf()
        claims.computeIfPresent(
            AcpConstant.tokenClaimsScope, fun(_: String, value: Any?): Any? {
                return if (value !is String) {
                    value
                } else {
                    value.split(" ").onEach { scope ->
                        authorities.add(SimpleGrantedAuthority("SCOPE_$scope"))
                    }
                }
            }
        )
        claims.computeIfPresent(
            AcpConstant.tokenClaimsAuthorities, fun(_: String, value: Any?): Any? {
                return if (value !is Collection<*>) {
                    value
                } else {
                    value.forEach { authority ->
                        authorities.add(SimpleGrantedAuthority("$authority"))
                    }
                }
            }
        )
        return OAuth2IntrospectionAuthenticatedPrincipal(claims, authorities)
    }

    @Throws(OAuth2AuthenticationException::class)
    fun encryptUserInfo(userInfoVo: TokenUserInfoVo): String = try {
        Base64.toBase64String(objectMapper.writeValueAsBytes(userInfoVo))
    } catch (e: Exception) {
        throw OAuth2AuthenticationException(OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, e.message, null), e)
    }

    @Throws(OAuth2AuthorizationException::class)
    fun decryptUserInfo(ciphertext: String): TokenUserInfoVo = try {
        objectMapper.readValue(Base64.decode(ciphertext), TokenUserInfoVo::class.java)
    } catch (e: Exception) {
        throw OAuth2AuthorizationException(OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, e.message, null), e)
    }

    @Throws(OAuth2AuthorizationException::class)
    fun getAuthenticatedPrincipal(bearerTokenAuthentication: BearerTokenAuthentication): OAuth2IntrospectionAuthenticatedPrincipal =
        bearerTokenAuthentication.principal as? OAuth2IntrospectionAuthenticatedPrincipal
            ?: throw OAuth2AuthorizationException(OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, "invalid token", null))

    @Throws(OAuth2AuthorizationException::class)
    fun getUserInfoFromToken(bearerTokenAuthentication: BearerTokenAuthentication): TokenUserInfoVo =
        (getAuthenticatedPrincipal(bearerTokenAuthentication).getClaim(
            AcpConstant.tokenClaimsUserinfo
        ) as? String)?.let { claimValue ->
            try {
                decryptUserInfo(claimValue)
            } catch (e: Exception) {
                logAdapter.error(e.message, e)
                TokenUserInfoVo()
            }
        } ?: TokenUserInfoVo()
}